package staff.console;

import static staff.logic.Employee.EMPLOYE_HEADER;
import static staff.util.StringUtils.getMaxStringLenght;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;

import staff.io.DOMEmployeLoader;
import staff.io.DOMEmployeSaver;
import staff.io.EmployeLoader;
import staff.io.EmployeSaver;
import staff.io.MySQLEmployeSaver;
import staff.io.SAXEmployeLoader;
import staff.io.TxtEmployeLoader;
import staff.io.TxtEmployeSaver;
import staff.logic.Employee;
import staff.logic.EmployeSystem;

public abstract class EmployeSystemCommand {
    protected EmployeSystem employeSystem;
    protected String description;

    public EmployeSystemCommand(EmployeSystem employeSystem) {
        this.employeSystem = employeSystem;
    }

    protected void setDesciption(String description) {
        this.description = description;
    }

    protected String getDesciption() {
        return description;
    }

    /** For some employe system action */
    abstract public void execute(String[] params);

    /** Static method for check parameters count */
    static void assertParamCount(String[] params, int assertCount) {
        if ((assertCount == 0) && (params != null) && (params.length != 0)) {
            throw new IllegalArgumentException("Parameters not allowed in this command");
        }
        if ((assertCount > 0) && ((params == null) || (params.length == 0))) {
            throw new IllegalArgumentException("This command require parameters. Type help for details");
        }
        if (params != null && assertCount != params.length) {
            throw new IllegalArgumentException("Wrong parameters count: " + params.length + ", required " + assertCount);
        }
    }
}

/** Quit from system */
class QuitSystem extends EmployeSystemCommand {
    public QuitSystem(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("quit from program");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 0);
        System.exit(0);
    }
}

/** Print employe system to default print stream */
class PrintAll extends EmployeSystemCommand {
    private PrintStream printStream;

    public PrintAll(EmployeSystem employeSystem, PrintStream printStream) {
        super(employeSystem);
        this.printStream = printStream;
        setDesciption("print all employe");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 0);
        employeSystem.print(printStream);
    }
}

/** Sort employe system by employe id (default sort order) */
class Sort extends EmployeSystemCommand {
    public Sort(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("sort employe by employe ID");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 0);
        employeSystem.sort();
    }
}

/** Sort employe system by name */
class SortByName extends EmployeSystemCommand {
    public SortByName(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("sort employe by full name");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 0);
        employeSystem.sort(Employee.FULL_NAME_ORDER);
    }
}

/** Sort employe system by hire date */
class SortByHireDate extends EmployeSystemCommand {
    public SortByHireDate(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("sort employe by hire date");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 0);
        employeSystem.sort(Employee.HIRE_DATE_ORDER);
    }
}

/** Load employes from text file */
class LoadFromTxt extends EmployeSystemCommand {
    public LoadFromTxt(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("load employes from text file. Params: <text file name>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        String fileName = params[0];
        try {
            EmployeLoader loader = new TxtEmployeLoader(fileName);
            employeSystem.load(loader);
        } catch (FileNotFoundException ex) {
            throw new IllegalArgumentException(ex);
        }
    }
}

/** Save employes to text file */
class SaveToTxt extends EmployeSystemCommand {
    public SaveToTxt(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("save employes to text file. Params: <text file name>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        String fileName = params[0];
        try {
            EmployeSaver saver = new TxtEmployeSaver(fileName);
            employeSystem.save(saver);
        } catch (IOException ex) {
            throw new IllegalStateException(ex);
        }
    }
}

/** Load employes from XML file. Used DOM */
class DOMLoadFromXML extends EmployeSystemCommand {
    public DOMLoadFromXML(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("load employes from XML file with usage DOM. Params: <XML file name>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        String fileName = params[0];
        try {
            EmployeLoader loader = new DOMEmployeLoader(fileName);
            employeSystem.load(loader);
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
}

/** Save employes to XML file. Used DOM */
class DOMSaveToXML extends EmployeSystemCommand {
    public DOMSaveToXML(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("save employes to XML file with usage DOM. Params: <XML file name>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        String fileName = params[0];
        try {
            EmployeSaver saver = new DOMEmployeSaver(fileName);
            employeSystem.save(saver);
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
}

/** Load employes from XML file. Used SAX */
class SAXLoadFromXML extends EmployeSystemCommand {
    public SAXLoadFromXML(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("load employes from XML file with usage SAX. Params: <XML file name>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        String fileName = params[0];
        try {
            EmployeLoader loader = new SAXEmployeLoader(fileName);
            employeSystem.load(loader);
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
}

/** Save employes to MySQL database */
class MySQLSaveToDB extends EmployeSystemCommand {
    public MySQLSaveToDB(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("save employes to MySQL database. Params: <connection> <user> <password>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 3);
        String connection = params[0];
        String user = params[1];
        String password = params[2];
        try {
            EmployeSaver saver = new MySQLEmployeSaver(connection, user, password);
            employeSystem.save(saver);
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }
}

/** Clear employe system */
class Clear extends EmployeSystemCommand {
    public Clear(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("clear employe list");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 0);
        employeSystem.clear();
    }
}

/** Remove employe from system by employe id */
class Remove extends EmployeSystemCommand {
    public Remove(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("remove employe from list. Params: <employe ID>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        int employeId = Integer.parseInt(params[0]);
        employeSystem.remove(employeId);
    }
}

/** Set employe as worker */
class SetAsWorker extends EmployeSystemCommand {
    public SetAsWorker(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("set employe as worker. Params: <employe ID>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        int employeId = Integer.parseInt(params[0]);
        Employee employe = employeSystem.get(employeId);
        employe.setAsWorker();
    }
}

/** Set employe as manager */
class SetAsManager extends EmployeSystemCommand {
    public SetAsManager(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("set employe as manager. Params: <employe ID>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        int employeId = Integer.parseInt(params[0]);
        Employee employe = employeSystem.get(employeId);
        employe.setAsManager();
    }
}

/** Set employe as other */
class SetAsOther extends EmployeSystemCommand {
    public SetAsOther(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("set employe as other. Params: <employe ID> <position description>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 2);
        int employeId = Integer.parseInt(params[0]);
        Employee employe = employeSystem.get(employeId);
        String positionDescription = params[1];
        employe.setAsOther(positionDescription);
    }
}

/** Print employe */
class PrintEmploye extends EmployeSystemCommand {
    private PrintStream printStream;

    public PrintEmploye(EmployeSystem employeSystem, PrintStream printStream) {
        super(employeSystem);
        this.printStream = printStream;
        setDesciption("print selected employe. Params: <employe ID>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        int employeId = Integer.parseInt(params[0]);
        Employee employe = employeSystem.get(employeId);
        printStream.println(EMPLOYE_HEADER);
        printStream.println(employe);
    }
}

/** Print manager for this employe */
class PrintManager extends EmployeSystemCommand {
    private PrintStream printStream;

    public PrintManager(EmployeSystem employeSystem, PrintStream printStream) {
        super(employeSystem);
        this.printStream = printStream;
        setDesciption("print manager for selected employe. Params: <employe ID>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        int employeId = Integer.parseInt(params[0]);
        Employee employe = employeSystem.get(employeId);
        Employee manager = employe.getManager(employeSystem);
        printStream.println(EMPLOYE_HEADER);
        printStream.println(manager);
    }
}

/** Print subordinate for this employe */
class PrintSubordinate extends EmployeSystemCommand {
    private PrintStream printStream;

    public PrintSubordinate(EmployeSystem employeSystem, PrintStream printStream) {
        super(employeSystem);
        this.printStream = printStream;
        setDesciption("print subordinate for selected employe. Params: <employe ID>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 1);
        int employeId = Integer.parseInt(params[0]);
        Employee employe = employeSystem.get(employeId);
        List<Employee> subordinate = employe.getSubordinate(employeSystem);
        printStream.println(EMPLOYE_HEADER);
        for (Employee current : subordinate) {
            printStream.println(current);
        }
    }
}

/** Set manager for employe */
class SetManager extends EmployeSystemCommand {
    public SetManager(EmployeSystem employeSystem) {
        super(employeSystem);
        setDesciption("set manager for employe. Params: <employe ID> <manager ID>");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 2);
        int employeId = Integer.parseInt(params[0]);
        Employee employe = employeSystem.get(employeId);
        int managerId = Integer.parseInt(params[1]);
        employe.setManagerId(managerId);
    }
}

/** Print help for commands */
class PrintHelp extends EmployeSystemCommand {
    Map<String, EmployeSystemCommand> commands;
    PrintStream printStream;

    public PrintHelp(Map<String, EmployeSystemCommand> commands, PrintStream printStream) {
        super(null);
        this.commands = commands;
        this.printStream = printStream;
        setDesciption("print this help");
    }

    @Override
    public void execute(String[] params) {
        assertParamCount(params, 0);
        // get max length of command name
        int commandColumnSize = getMaxStringLenght(commands.keySet());
        // format output
        String format = "%-" + commandColumnSize + "s - %s\n";
        for (Map.Entry<String, EmployeSystemCommand> entry : commands.entrySet()) {
            printStream.printf(format, entry.getKey(), entry.getValue().getDesciption());
        }
    }

}
